﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;
using System.Xml;

namespace Client_Server_Interface
{
    public class DeviceTree
    {
        //public DeviceTree(List<Region> regionList, string status = "ok")
        //{
        //    Regions = regionList;
        //    Status = status;
        //}

        public DeviceTree(List<DeviceTreeItem> deviceTreeItems, string status = "ok")
        {
            DeviceTreeItems = deviceTreeItems;
            Status = status;
        }

        public DeviceTree(string status = "creating")
        {
            Status = status;
            DeviceTreeItems = new List<DeviceTreeItem>();
        }

        public string Status { get; set; }
        //public List<Region> Regions { get; private set; }
        public List<DeviceTreeItem> DeviceTreeItems { get; private set; }

        public void AddDeviceTreeItem(DeviceTreeItem item)
        {
            //if(item.AdditionalInfo.GetType() == typeof(DeviceTreeItem.RegionInfo))
            //    DeviceTreeItems.Add(item);
            //else
            //{
            //    IncorrectInputValueException.Throw(item.TypeItem + " to DeviceTree", ClientExceptionMessage.INCORRECT_SUBITEM_ADDED);
            //}
            if(item.TypeItem != DeviceTreeItem.ItemType.Region)
                IncorrectInputValueException.Throw(item.TypeItem + " to DeviceTree", ClientExceptionMessage.INCORRECT_SUBITEM_ADDED);
            DeviceTreeItems.Add(item);
        }

        public static DeviceTree CreateTreeFromXml(byte[] treeContent)
        {
            var memStream = new MemoryStream(treeContent);

            var reader = XmlReader.Create(memStream);

            DeviceTreeItem curRegion = null;
            DeviceTreeItem curStation = null;
            var deviceTree = new DeviceTree();

            while (true)
            {
                try
                {
                    if (!reader.Read())
                        break;
                }
                catch (Exception)
                {
                    ServerRequestException.Throw(ServerRequestException.ExceptionSubject.Error, ClientExceptionMessage.INVALID_TREE_RECEIVED);
                    //Перезапросить дерево устройств и снова пытаться его прочитать
                }
                if (reader.NodeType == XmlNodeType.Element)
                {
                    switch (reader.Name)
                    {
                        case DeviceTreeItem.XmlTreeElementName.REGION:                  //Region Node
                            //Create next Region Node
                            if (curRegion != null)
                                deviceTree.AddDeviceTreeItem(curRegion);
                            curRegion = new DeviceTreeItem();

                            while (reader.MoveToNextAttribute())
                            {
                                switch (reader.Name)
                                {
                                    //Append Name to the current Region
                                    case DeviceTreeItem.XmlTreeAttributeName.NAME:
                                        reader.ReadAttributeValue();
                                        curRegion.Header = reader.Value;
                                        curRegion.AdditionalInfo = new DeviceTreeItem.RegionInfo
                                        {
                                            Region = reader.Value
                                        };
                                        break;
                                }
                            }
                            break;

                        case DeviceTreeItem.XmlTreeElementName.STATION:                         //Station Node
                            //Create next Station Node
                            if (curStation != null)
                                curRegion.AddSubItem(curStation);
                            curStation = new DeviceTreeItem
                            {
                                AdditionalInfo = new DeviceTreeItem.StationInfo
                                {
                                    ParentInfo = (curRegion.AdditionalInfo as DeviceTreeItem.RegionInfo).GetInfo
                                },
                                ParentItem = curRegion,
                                TypeItem = DeviceTreeItem.ItemType.Station
                            };
                            while (reader.MoveToNextAttribute())
                            {
                                switch (reader.Name)
                                {
                                    //Append Name to the current Station
                                    case DeviceTreeItem.XmlTreeAttributeName.NAME:
                                        reader.ReadAttributeValue();
                                        curStation.Header = reader.Value;
                                        (curStation.AdditionalInfo as DeviceTreeItem.StationInfo).Station = reader.Value;
                                        break;

                                    //Append Coordinates to the current Station
                                    case DeviceTreeItem.XmlTreeAttributeName.COORDINATES:
                                        reader.ReadAttributeValue();
                                        (curStation.AdditionalInfo as DeviceTreeItem.StationInfo).Coordinates =
                                            reader.Value;
                                        break;

                                    //Append BLR info to the current Station
                                    case DeviceTreeItem.XmlTreeAttributeName.BLR:
                                        reader.ReadAttributeValue();
                                        (curStation.AdditionalInfo as DeviceTreeItem.StationInfo).BLR = reader.Value;
                                        break;
                                }
                            }
                            break;

                        case DeviceTreeItem.XmlTreeElementName.DEVICE:                          //Device Node
                            var curDevice = new DeviceTreeItem
                            {
                                AdditionalInfo = new DeviceTreeItem.DeviceInfo
                                {
                                    ParentInfo =
                                        (curStation.AdditionalInfo as DeviceTreeItem.StationInfo).GetInfo,
                                    Region = curRegion.Header
                                        
                                },
                                ParentItem = curStation,
                                TypeItem = DeviceTreeItem.ItemType.Device
                            };
                            while (reader.MoveToNextAttribute())
                            {
                                switch (reader.Name)
                                {
                                    case DeviceTreeItem.XmlTreeAttributeName.NAME:
                                        reader.ReadAttributeValue();
                                        (curDevice.AdditionalInfo as DeviceTreeItem.DeviceInfo).Device = reader.Value;
                                        curDevice.Header = reader.Value;
                                        break;

                                    case DeviceTreeItem.XmlTreeAttributeName.IsACTIVE:
                                        reader.ReadAttributeValue();
                                        (curDevice.AdditionalInfo as DeviceTreeItem.DeviceInfo).IsActive = bool.Parse(reader.Value);
                                        break;

                                    case DeviceTreeItem.XmlTreeAttributeName.IP:
                                        reader.ReadAttributeValue();
                                        (curDevice.AdditionalInfo as DeviceTreeItem.DeviceInfo).IpAddress = reader.Value;
                                        break;

                                    case DeviceTreeItem.XmlTreeAttributeName.LibName:
                                        reader.ReadAttributeValue();
                                        (curDevice.AdditionalInfo as DeviceTreeItem.DeviceInfo).LibraryName = reader.Value;
                                        break;
                                    case DeviceTreeItem.XmlTreeAttributeName.DEVICE_TITLE:
                                        reader.ReadAttributeValue();
                                        (curDevice.AdditionalInfo as DeviceTreeItem.DeviceInfo).Title = reader.Value;
                                        break;
                                }
                            }
                            //Add device to list
                            curStation.AddSubItem(curDevice);
                            break;
                    }
                }

                else if (reader.NodeType == XmlNodeType.EndElement &&
                         reader.Name == DeviceTreeItem.XmlTreeElementName.REGIONS)
                    break;

                //else if (reader.NodeType == XmlNodeType.EndElement)
                //{
                //    switch (reader.Name)
                //    {
                //        case DeviceTreeItem.XmlTreeElementName.REGION:
                //            deviceTree.AddDeviceTreeItem(curRegion);
                //            break;

                //        case DeviceTreeItem.XmlTreeElementName.STATION:
                //            curRegion.AddSubItem(curStation);
                //            break;

                //        case DeviceTreeItem.XmlTreeElementName.DEVICE:
                //            curStation.AddSubItem(curDevice);
                //            break;
                //    }
                //}

            }
            curRegion.AddSubItem(curStation);
            deviceTree.AddDeviceTreeItem(curRegion);
            deviceTree.Status = "ok";
            return deviceTree;
        }

    }

    public class DeviceTreeItem
    {
        public class XmlTreeElementName
        {
            public const string REGIONS = "Regions";
            public const string REGION = "Region";
            public const string STATION = "RTPS";
            public const string DEVICE = "Device";
        }

        public class XmlTreeAttributeName
        {
            public const string NAME = "Name";
            public const string COORDINATES = "Coordinates";
            public const string BLR = "BLR";
            public const string IsACTIVE = "Active";
            public const string IP = "IpAddress";
            public const string INFO = "AdditionalInfo";
            public const string LibName = "LibraryName";
            public const string DEVICE_TITLE = "DeviceTitle";
        }

        public enum ItemType
        {
            Region,
            Station,
            Device
        }

        public DeviceTreeItem()
        {
            ParentItem = null;
            SubItems = new List<DeviceTreeItem>();
        }

        public string Header { get; set; }
        public ItemType TypeItem { get; set; }
        public object AdditionalInfo { get; set; }
        public DeviceTreeItem ParentItem { get; set; }
        public List<DeviceTreeItem> SubItems { get; private set; }

        private void DetermineItemType(ref DeviceTreeItem item)
        {
            var t = item.AdditionalInfo.GetType();
            if (t == typeof(RegionInfo))
                item.TypeItem = ItemType.Region;
            else if (t == typeof(StationInfo))
                item.TypeItem = ItemType.Station;
            else if (t == typeof(DeviceInfo))
                item.TypeItem = ItemType.Device;
            else
            {
                IncorrectInputValueException.Throw("WrongTreeItem", t.ToString());
            }
        }

        public void AddSubItem(DeviceTreeItem item)
        {
            DetermineItemType(ref item);
            switch (item.TypeItem)
            {
                case ItemType.Region:
                    //if(item.TypeItem != ItemType.Station)
                    IncorrectInputValueException.Throw(item.TypeItem + " to " + TypeItem, ClientExceptionMessage.INCORRECT_SUBITEM_ADDED);
                    break;
                case ItemType.Station:
                    if(TypeItem != ItemType.Region)
                        IncorrectInputValueException.Throw(item.TypeItem + " to " + TypeItem, ClientExceptionMessage.INCORRECT_SUBITEM_ADDED);
                    break;
                case ItemType.Device:
                    if (TypeItem != ItemType.Station)
                        IncorrectInputValueException.Throw(item.TypeItem + " to " + TypeItem, ClientExceptionMessage.INCORRECT_SUBITEM_ADDED);
                    break;
            }
            SubItems.Add(item);
        }

        public override string ToString()
        {
            return Header;
        }

        //Info Classes
        public class RegionInfo
        {
            public string Region { get; set; }

            public string MainStation { get; set; }

            public RegionInfo GetInfo
            {
                get
                {
                    return new RegionInfo
                        {
                            MainStation = MainStation,
                            Region = Region
                        };
                }
            }

            public override string ToString()
            {
                return Region;
            }

            public override int GetHashCode()
            {
                return Region.GetHashCode() ^ MainStation.GetHashCode();
            }
        }

        public class StationInfo
        {
            protected bool Equals(StationInfo other)
            {
                return string.Equals(_coordinates, other._coordinates) &&
                       string.Equals(_customCoordinates, other._customCoordinates) && string.Equals(BLR, other.BLR) &&
                       string.Equals(Station, other.Station) && string.Equals(Tag, other.Tag) &&
                       Equals(ParentInfo, other.ParentInfo);
            }

            public override int GetHashCode()
            {
                unchecked
                {
                    int hashCode = (_coordinates != null ? _coordinates.GetHashCode() : 0);
                    hashCode = (hashCode*397) ^ (_customCoordinates != null ? _customCoordinates.GetHashCode() : 0);
                    hashCode = (hashCode*397) ^ (BLR != null ? BLR.GetHashCode() : 0);
                    hashCode = (hashCode*397) ^ (Station != null ? Station.GetHashCode() : 0);
                    hashCode = (hashCode*397) ^ (Tag != null ? Tag.GetHashCode() : 0);
                    hashCode = (hashCode*397) ^ (ParentInfo != null ? ParentInfo.GetHashCode() : 0);
                    return hashCode;
                }
            }

            private string _coordinates = string.Empty;
            private string _customCoordinates = string.Empty;

            public string BLR { get; set; }
            public string Station { get; set; }
            public string Tag { get; set; }
            public string Coordinates
            {
                get { return _coordinates; }
                set
                {
                    var len = value.Length;
                    if (len != 14 && len != 12 && len != 16 && len != 0)
                        IncorrectInputValueException.Throw(ClientExceptionMessage.COORDINATES_WRONG_STRING, "coordinates value - " + value + " is incorrect.");
                    _coordinates = value;
                }
            }

            public string CustomCoordinates
            {
                get { return _customCoordinates; }
                set
                {
                    var len = value.Length;
                    if (len != 14 && len != 12 && len != 16 && len != 0)
                        IncorrectInputValueException.Throw(ClientExceptionMessage.COORDINATES_WRONG_STRING, "coordinates value - " + value + " is incorrect.");
                    _customCoordinates = value;
                }
            }

            public string CoordinatesA
            {
                get
                {
                    return _coordinates != string.Empty ? _coordinates.Substring(0, _coordinates.Length / 2) : _coordinates;
                }
            }

            public string CoordinatesB
            {
                get
                {
                    return _coordinates != string.Empty ? _coordinates.Substring(_coordinates.Length / 2, _coordinates.Length / 2) : _coordinates;
                }
            }

            public RegionInfo ParentInfo { get; set; }

            public override string ToString()
            {
                return Station;
            }
            public override bool Equals(object obj)
            {
                if (ReferenceEquals(null, obj)) return false;
                if (ReferenceEquals(this, obj)) return true;
                if (obj.GetType() != this.GetType()) return false;
                return Equals((StationInfo) obj);
            }
            public StationInfo GetInfo
            {
                get
                {
                    return new StationInfo
                    {
                        BLR = BLR,
                        Station = Station,
                        Coordinates = _coordinates
                    };
                }
            }
        }

        public class DeviceInfo
        {
            private string _ipAddress;

            public string Title { get; set; }
            public string Device { get; set; }
            public string Tag { get; set; }
            public bool IsActive { get; set; }
            public string IpAddress
            {
                get { return _ipAddress; }
                set
                {
                    IPAddress ip;
                    const string mask = "[0-9]{0,3}\\.[0-9]{0,3}\\.[0-9]{0,3}\\.[0-9]{0,3}";
                    if (IPAddress.TryParse(value, out ip) && Regex.IsMatch(value, mask))
                    {
                        _ipAddress = value;
                    }
                }
            }
            public string Region { get; set; }

            public string LibraryName { get; set; }

            public StationInfo ParentInfo { get; set; }

            public override int GetHashCode()
            {
                return ParentInfo.Station.GetHashCode() ^ (Title == null ? 0 : Title.GetHashCode()) ^ IpAddress.GetHashCode();
            }

            public override string ToString()
            {
                return Device;
            }
        }

        public class RegionNotFoundException : Exception
        {
            public RegionNotFoundException(string message)
                : base(message)
            {
            }

            public RegionNotFoundException()
            {

            }
        }
        //-----------

        

    }
}
